/*
 *  alias.cpp
 *  osnutshell
 *
 *  Created by Zachary  Heylmun on 3/28/10.
 *  Copyright 2010 University of Florida. All rights reserved.
 */

#include "Alias.h"

using std::string;
using std::vector;
using std::map;
using std::cout;

namespace nutshell
{
	// helper for split_string
	bool is_space(char c)
	{
		return isspace(c);
	}

	// helper for split_string
	bool not_space(char c)
	{
		return !isspace(c);
	}

	// splits a string into a vector of strings
	// delimited by whitespace
	vector<string> split_string(const string& str)
	{
		vector<string> res;
	
		string::const_iterator itr = str.begin();
		string::const_iterator jtr = itr;
	
		while (itr != str.end())
		{
			itr = std::find_if(itr, str.end(), not_space);
			jtr = std::find_if(itr, str.end(), is_space);
		
			if (itr != str.end())
				res.push_back( string(itr, jtr) );
			
			itr = jtr;
		}
	
		return res;
	}

	// parses the provided str (alias command argument) into the outparameter
	// name and expansion
	bool parse_alias( const string& str, string& nameout, vector<string>& expansionout )
	{
		nameout = "";
		expansionout.clear();

		// find the left hand side of the =
		string::const_iterator itr = str.begin();
		string::const_iterator jtr = itr;
	
		itr = std::find_if(itr, str.end(), not_space);
		jtr = std::find(itr, str.end(), '=');
	
		if (itr == jtr || jtr == str.end())
			return false;
		
		// split the left hand side
		vector<string> lhs = split_string( string(itr, jtr) );
		// if there are more than one token on the left hand side, it is a bad argument
		if (lhs.size() != 1)
			return false;
	
		// the alias name
		nameout = lhs[0];
	
		// find the first quote
		itr = std::find(itr, str.end(), '"');
		//if no quotes found, it is a bad argument
		if ( itr < jtr || itr == str.end() )
			return false;
		
		// find the second quote
		jtr = std::find(itr + 1, str.end(), '"');
		// if not second quote found, it is a bad argument
		if (jtr == str.end())
			return false;
		
		// split the string between the quotes, to create the alias expansion
		expansionout = split_string( string(itr + 1, jtr) );
	
		return true;
	}

	// BUILT-IN COMMAND FUNCTION FOR alias AND unalias
	void Alias::do_command(int argc, char** argv)
	{
		string command_name(argv[0]);
	
		// if alias with no arguments, print current aliases
		if ( argc == 1 && command_name == "alias" )
		{
			list_aliases();
		}
		else if( argc > 1 && command_name == "alias" )
		{
			string word;
			vector<string> expansion;
		
			// combine the arguments into a string
			string aliasStr;
			for( int i = 1; i < argc; ++i)
			{
				aliasStr += " ";
				aliasStr += argv[i];
			}
		
			// parse the argument string
			if ( !parse_alias(aliasStr, word, expansion) )
			{
				// improper usage
				cout << "USAGE: alias word = \"string\"" << std::endl;
				return;
			}
		
			vector<string> prev;
			bool do_replace = false;
			
			// does an alias with this name already exist?
			if ( aliases.find(word) != aliases.end() )
			{
				// ask if user wants to replace alias
				cout << "An alias already exists for " << word << ". Would you like to replace it?\n";
				if (Scanner::promptYesNo())
				{
					// user wants to replace alias
					do_replace = true;
					prev = aliases[word]; //store previous alias in case the new one is bad
				}
				else
				{
					// user does not want to replace alias
					cout << "No changes will be made." << std::endl;
					return;
				}
			}
		
			// add the new alias
			aliases[word] = expansion;
		
			// check if an alias loop has been created
			if (contains_loop(word))
			{
				// a loop has been created
				cout << "Infinite alias expansion detected. No changes will be made." << std::endl;
				if (do_replace)
					aliases[word] = prev; // restore previous alias
				else
					aliases.erase(word);  // delete the new alias
			}
		}
		else if (argc == 2 && command_name == "unalias")
		{
			// remove the alias
			string word(argv[1]);
		
			unalias(word);
		}
		else if (command_name == "alias")
		{
			cout << "USAGE: alias word = \"string\"" << std::endl;
			return;
		}
		else if (command_name == "unalias")
		{
			cout << "USAGE: unaliad word" << std::endl;
			return;
		}
	}

	// returns true iff the word is an alias name
	bool Alias::is_alias( string word )
	{
		return aliases.find(word) != aliases.end();
	}

	// returns a vector of strings that is the recursive expansion
	// of the alias with name word
	vector<string> Alias::expand_alias( std::string word )
	{
		vector<string> exp = aliases[word];
	
		if (exp.empty())
			return exp;
		
		while (aliases.find(exp[0]) != aliases.end())
		{
			vector<string>& replacement = aliases[exp[0]];
			exp.erase(exp.begin());
			exp.insert(exp.begin(), replacement.begin(), replacement.end());
		}
	
		return exp;
	}

	// returns true iff the alias with name a
	// is part of an alias loop
	bool Alias::contains_loop( const string& a )
	{
		if (aliases.find(a) == aliases.end())
			return false;
		
		string exp_cmd = a;
	
		map<string, bool> visits;
	
		while(aliases.find(exp_cmd) != aliases.end() && !aliases[exp_cmd].empty())
		{
			if (visits.find(exp_cmd) != visits.end())
				return true;
		
			visits[exp_cmd] = true;
			exp_cmd = aliases[exp_cmd][0];
		}

		return false;
	}

	// removes the alias with name s
	// if ask=true, asks the user if dependant aliases should be removed
	// else, just removes dependant aliases
	void Alias::unalias(string s, bool ask)
	{
		// if the alias exists, remove it
		if (aliases.find(s) != aliases.end())
			aliases.erase(s);
		else
		{
			cout << s << " not found." << std::endl;
			return;
		}
	
		// search for dependant aliases
		for (map<string, vector<string> >::iterator iter = aliases.begin(); iter != aliases.end(); ++iter)
		{
			for (vector<string>::iterator jter = iter->second.begin(); jter != iter->second.end(); jter++)
			{
				if(*jter == s)
				{
					if (ask)
					{
						cout << "Would you like to delete the following dependant alias?\n\t";
						cout << iter->first << " = ";
						for ( vector<string>::const_iterator j = iter->second.begin(); j != iter->second.end(); ++j)
							cout << *j << " ";
						int ans = Scanner::promptYesNoAll();
						if (ans > 0)
							unalias(iter->first);
						else if (ans < 0)
							unalias(iter->first, false);
					}
					else 
					{
						unalias(iter->first, false);
					}
				}
			}
		}
	}

	// prints a list of aliases and their expansions to stdout
	void Alias::list_aliases()
	{
		for (map<string, vector < string> >::const_iterator i = aliases.begin(); i != aliases.end(); ++i)
		{
			cout << i->first << " = ";
			for ( vector<string>::const_iterator j = i->second.begin(); j != i->second.end(); ++j)
				cout << *j << " ";
			cout << std::endl;
		}
	}
	
}
